package com.ry.interfaces;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class UNBlockingIO {

    public static void main(String[] args) throws IOException {
        serve(8080);
    }


    public static void serve(int port) throws IOException {

        // 1、获取通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();

        /**
         * 2.设置为非阻塞
         * 与Selector一起使用时，Channel必须处于非阻塞模式下，否则将抛出异常IllegalBlockingModeException。
         * 这意味着，FileChannel不能与Selector一起使用，
         * 因为FileChannel不能切换到非阻塞模式，而套接字相关的所有的通道都可以。
         */
        serverChannel.configureBlocking(false);
        // ServerSocket ss = serverChannel.socket();
        // ss.bind(new InetSocketAddress(port));
        serverChannel.bind(new InetSocketAddress(port));

        /**
         * 3、获取Selector选择器
         */
        Selector selector = Selector.open();

        // 4、将通道注册到选择器上,并制定监听事件为：“接收”事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        //死循环，一直select，然后遍历就绪的SelectionKey
        for (; ; ) {
            try {
                //
                selector.select();
            } catch (IOException ex) {
                ex.printStackTrace();
                break;
            }

            Set<SelectionKey> readyKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = readyKeys.iterator();

            //5.多路复用器无限循环就绪的Key
            while (iterator.hasNext()) {
                //获取key
                SelectionKey key = iterator.next();
                iterator.remove();

                try {

                    //6.连接-完成上述操作后，相当于完成了TCP的三次握手，TCP物理链路正式建立。
                    if (key.isAcceptable()) {
                        //注意，我们需要将新创建的SocketChannel设置为异步非阻塞，同时也可以对其TCP参数进行设置
                        // 若接受的事件是“接收就绪” 操作,就获取客户端连接
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        //SocketChannel client = serverChannel.accept();

                        //7. 切换为非阻塞模式
                        client.configureBlocking(false);

                        //8.将该通道注册到selector选择器上
                        final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
                        client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate());
                        System.out.println("Accepted connection from " + client);
                    }
                    //发消息
                    if (key.isWritable()) {
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        while (buffer.hasRemaining()) {
                            if (client.write(buffer) == 0) {
                                break;
                            }
                        }
                        client.close();
                    }

                    //读消息
                    if (key.isReadable()) {
                        //首先创建一个ByteBuffer，由于我们事先无法得知客户端发送的码流大小，
                        //作为例程，我们开辟一个1M的缓冲区。然后调用SocketChannel的read方法读取请求码流。
                        //注意，由于我们已经将SocketChannel设置为异步非阻塞模式，因此它的read是非阻塞的。
                        //使用返回值进行判断，看读取到的字节数
                        SocketChannel socketChannel = (SocketChannel) key.channel();

                        // 读取数据
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        int length = 0;
                        //length=socketChannel.read(byteBuffer)
                        //返回值有以下三种可能的结果
                        //返回值大于0：读到了字节，对字节进行编解码；
                        //返回值等于0：没有读取到字节，属于正常场景，忽略；
                        //返回值为-1：链路已经关闭，需要关闭SocketChannel，释放资源。
                        while ((length = socketChannel.read(byteBuffer)) != -1) {
                            //当读取到码流以后，我们进行解码，首先对readBuffer进行flip操作，
                            //它的作用是将缓冲区当前的limit设置为position，position设置为0，用于后续对缓冲区的读取操作。
                            //然后根据缓冲区可读的字节个数创建字节数组，
                            //调用ByteBuffer的get操作将缓冲区可读的字节数组复制到新创建的字节数组中，
                            //最后调用字符串的构造函数创建请求消息体并打印。
                            //如果请求指令是"QUERY TIME ORDER"则把服务器的当前时间编码后返回给客户端
                            byteBuffer.flip();

                            System.out.println(new String(byteBuffer.array(), 0, length));
                            byteBuffer.clear();
                        }
                        socketChannel.close();
                    }
                } catch (IOException ex) {
                    key.cancel();
                    try {
                        key.channel().close();
                    } catch (IOException cex) {
                        // 在关闭时忽略
                    }
                }
            }
        }
    }

}
